﻿using System;
using System.IO;
using System.Net;
using System.Text;
using Newtonsoft.Json;

namespace Syncronization.REST
{
    public static class RestHelpers
    {
        #region public

        /// <summary>
        /// Adds the header.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="headerName">Name of the header.</param>
        /// <param name="value">The value.</param>
        public static void AddHeader(HttpWebRequest request, string headerName, string value)
        {
            request.Headers.Add(headerName, value);
        }

        /// <summary>
        /// Adds to request.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="serializer">The serializer.</param>
        /// <param name="itemToAdd">The item to add.</param>
        public static void AddToRequest(HttpWebRequest request, JsonSerializer serializer, object itemToAdd)
        {
            using (var sw = new StreamWriter(request.GetRequestStream()))
            {
                using (JsonWriter writer = new JsonTextWriter(sw))
                {
                    serializer.Serialize(writer, itemToAdd);
                }
            }
        }

        /// <summary>
        /// Authorizations the token.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        public static string AuthorizationToken(WebRequest request)
        {
            string authToken = request.Headers[RestHeaders.Authorization];

            byte[] buffer = Convert.FromBase64String(authToken);

            return new UTF8Encoding().GetString(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Creates the HTTP request.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="httpMethod">The HTTP method.</param>
        /// <param name="requestTimeout">The request timeout.</param>
        /// <returns></returns>
        public static HttpWebRequest CreateHttpRequest(string url, HttpMethods httpMethod, TimeSpan requestTimeout)
        {
            return CreateHttpRequest(new Uri(url), httpMethod.ToString(), requestTimeout);
        }

        /// <summary>
        /// Creates the HTTP request.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <param name="httpMethod">The HTTP method.</param>
        /// <param name="requestTimeout">The timeout.</param>
        /// <returns></returns>
        public static HttpWebRequest CreateHttpRequest(Uri uri, HttpMethods httpMethod, TimeSpan requestTimeout)
        {
            return CreateHttpRequest(uri, httpMethod.ToString(), requestTimeout);
        }

        /// <summary>
        /// Creates the HTTP request.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <param name="httpMethod">The HTTP method.</param>
        /// <param name="requestTimeout">The timeout.</param>
        /// <returns></returns>
        public static HttpWebRequest CreateHttpRequest(Uri uri, string httpMethod, TimeSpan requestTimeout)
        {
            var request = (HttpWebRequest) WebRequest.Create(uri);
            request.Timeout = (int) requestTimeout.TotalMilliseconds;
            request.ReadWriteTimeout = (int) requestTimeout.TotalMilliseconds;
            request.Method = httpMethod;

            request.ContentType = "application/json; charset=utf-8";

            //need to set this for the compact framework. By default the compact
            //framework has AllowWriteStreamBuffering set to false which cause us problems
            //unless we are chunking the data across the boundry
            request.AllowWriteStreamBuffering = true;
            request.SendChunked = false;

            return request;
        }

        /// <summary>
        /// Creates the HTTP request.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <param name="httpMethod">The HTTP method.</param>
        /// <param name="requestTimeout">The timeout.</param>
        /// <param name="apiKey">The API key.</param>
        /// <returns></returns>
        public static HttpWebRequest CreateHttpRequest(Uri uri, HttpMethods httpMethod, TimeSpan requestTimeout,
                                                       string apiKey)
        {
            HttpWebRequest request = CreateHttpRequest(uri, httpMethod, requestTimeout);

            AddHeader(request, RestHeaders.ApiKey, apiKey);

            return request;
        }

        /// <summary>
        /// Creates the HTTP request.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <param name="httpMethod">The HTTP method.</param>
        /// <param name="requestTimeout">The request timeout.</param>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        public static HttpWebRequest CreateHttpRequest(Uri uri, HttpMethods httpMethod, TimeSpan requestTimeout,
                                                       string userName, string password)
        {
            string authToken = GenerateAuthToken(userName, password);

            HttpWebRequest request = CreateHttpRequest(uri, httpMethod, requestTimeout);

            AddHeader(request, RestHeaders.Authorization, authToken);

            return request;
        }

        /// <summary>
        /// Downloads the data.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="stream">The stream.</param>
        public static long? DownloadData(WebRequest request, Stream stream)
        {
            using (var response = (HttpWebResponse) request.GetResponse())
            {
                if (response.StatusCode == HttpStatusCode.OK
                    || response.StatusCode == HttpStatusCode.PartialContent)
                {
                    if (stream != null)
                    {
                        using (Stream responseStream = response.GetResponseStream())
                        {
                            long bytesCopied = CopyStream(responseStream, stream);
                            return bytesCopied;
                        }
                    }
                }
            }
            return null;
        }

        #endregion

        #region internal

        internal static long CopyStream(Stream sourceStream, Stream destinationStream)
        {
            const int BufferSize = 0x10000;
            var buffer = new byte[BufferSize];
            int n = 0;
            long totalRead = 0;
            do
            {
                n = sourceStream.Read(buffer, 0, BufferSize);
                if (n > 0)
                {
                    totalRead += n;
                    destinationStream.Write(buffer, 0, n);
                }
            } while (n > 0);
            return totalRead;
        }

        #endregion

        #region private

        /// <summary>
        /// Generates the auth token.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        private static string GenerateAuthToken(string userName, string password)
        {
            string authToken = string.Format("{0}:{1}", userName, password);

            byte[] credentialBuffer = new UTF8Encoding().GetBytes(authToken);

            return Convert.ToBase64String(credentialBuffer);
        }

        #endregion
    }
}